<template>
  <div class="dvz-scroll-ranking-board" :ref="ref">
    <div
      class="row-item"
      v-for="(item, i) in rows"
      :key="item.toString() + item.scroll"
      :style="`height: ${heights[i]}px;`"
      @mouseover="stopAnimation"
      @mouseleave="animation"
    >
      <div class="ranking-info">
        <div class="rank">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="18"
            height="18"
            viewBox="0 0 32 32"
          >
            <path
              id="x"
              d="M16.1,32l-0.5-0.3C3.2,27.6,1.6,21.9,1.6,18.3V3.9h1C11.4,4.1,15,0.8,15,0.8l1-0.8l0.8,0.8 c0,0,3.6,3.1,11.4,3.1l0,0c0.3,0,0.5,0,1,0h1v14.5c0,3.9-1.5,9.3-13.9,13.4L16.1,32z"
              :fill="`${translateColor(item.ranking)}`"
            />
            <text
              style="fill: #ffffff; font-size: 22px"
              :x="translateX(item.ranking)"
              y="24"
            >
              {{ item.ranking }}
            </text>
          </svg>
        </div>
        <div class="info-name" v-html="item.name" />
        <div class="ranking-value">
          <span :style="`color:${translateColor(item.ranking)}`">{{
            mergedConfig.valueFormatter
              ? mergedConfig.valueFormatter(item)
              : item.value
          }}</span
          ><span style="margin-left: 5px">{{ mergedConfig.unit }}</span>
        </div>
      </div>

      <div class="ranking-column">
        <div
          class="inside-column"
          :style="`width: ${item.percent}%;background-color:${translateColor(
            item.ranking
          )}`"
        >
          <div class="shine" />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import autoResize from "@jiaminghi/data-view/src/mixin/autoResize.js";

import { deepMerge } from "@jiaminghi/charts/lib/util/index.js";

import { deepClone } from "@jiaminghi/c-render/lib/plugin/util.js";

export default {
  name: "DvzScrollRankingBoard",
  mixins: [autoResize],
  props: {
    config: {
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      ref: "scroll-ranking-board",

      defaultConfig: {
        colors: ["#ed2b26", "#ea4d25", "#ea6d26", "#ea8c1f"],
        /**
         * @description Board data
         * @type {Array<Object>}
         * @default data = []
         */
        data: [],
        /**
         * @description Row num
         * @type {Number}
         * @default rowNum = 5
         */
        rowNum: 5,
        /**
         * @description Scroll wait time
         * @type {Number}
         * @default waitTime = 2000
         */
        waitTime: 2000,
        /**
         * @description Carousel type
         * @type {String}
         * @default carousel = 'single'
         * @example carousel = 'single' | 'page'
         */
        carousel: "single",
        /**
         * @description Value unit
         * @type {String}
         * @default unit = ''
         * @example unit = 'ton'
         */
        unit: "",
        /**
         * @description Auto sort by value
         * @type {Boolean}
         * @default sort = true
         */
        sort: true,
        /**
         * @description Value formatter
         * @type {Function}
         * @default valueFormatter = null
         */
        valueFormatter: null,
      },

      mergedConfig: null,

      rowsData: [],

      rows: [],

      heights: [],

      animationIndex: 0,

      animationHandler: "",

      updater: 0,
    };
  },
  watch: {
    config() {
      const { stopAnimation, calcData } = this;

      stopAnimation();

      calcData();
    },
  },
  methods: {
    translateX(n) {
      return n < 10 ? 8.5 : 3;
    },
    translateColor(n) {
      return this.mergedConfig.colors.length >= n
        ? this.mergedConfig.colors[n - 1]
        : "#eaae1f";
    },
    afterAutoResizeMixinInit() {
      const { calcData } = this;

      calcData();
    },
    onResize() {
      const { mergedConfig, calcHeights } = this;

      if (!mergedConfig) return;

      calcHeights(true);
    },
    calcData() {
      const { mergeConfig, calcRowsData } = this;

      mergeConfig();

      calcRowsData();

      const { calcHeights } = this;

      calcHeights();

      const { animation } = this;

      animation(true);
    },
    mergeConfig() {
      let { config, defaultConfig } = this;

      this.mergedConfig = deepMerge(
        deepClone(defaultConfig, true),
        config || {}
      );
    },
    calcRowsData() {
      let { data, rowNum, sort } = this.mergedConfig;

      sort &&
        data.sort(({ value: a }, { value: b }) => {
          if (a > b) return -1;
          if (a < b) return 1;
          if (a === b) return 0;
        });

      const value = data.map(({ value }) => value);

      const min = Math.min(...value) || 0;

      // abs of min
      const minAbs = Math.abs(min);

      const max = Math.max(...value) || 0;

      // abs of max
      const maxAbs = Math.abs(max);

      const total = max + minAbs;

      data = data.map((row, i) => ({
        ...row,
        ranking: i + 1,
        percent: ((row.value + minAbs) / total) * 100,
      }));

      const rowLength = data.length;

      if (rowLength > rowNum && rowLength < 2 * rowNum) {
        data = [...data, ...data];
      }

      data = data.map((d, i) => ({ ...d, scroll: i }));

      this.rowsData = data;
      this.rows = data;
    },
    calcHeights(onresize = false) {
      const { height, mergedConfig } = this;

      const { rowNum, data } = mergedConfig;

      const avgHeight = height / rowNum;

      this.avgHeight = avgHeight;

      if (!onresize) this.heights = new Array(data.length).fill(avgHeight);
    },
    async animation(start = false) {
      let {
        avgHeight,
        animationIndex,
        mergedConfig,
        rowsData,
        animation,
        updater,
      } = this;

      const { waitTime, carousel, rowNum } = mergedConfig;

      const rowLength = rowsData.length;

      if (rowNum >= rowLength) return;

      if (start) {
        await new Promise((resolve) => setTimeout(resolve, waitTime));
        if (updater !== this.updater) return;
      }

      const animationNum = carousel === "single" ? 1 : rowNum;

      let rows = rowsData.slice(animationIndex);
      rows.push(...rowsData.slice(0, animationIndex));

      this.rows = rows.slice(0, rowNum + 1);
      this.heights = new Array(rowLength).fill(avgHeight);

      await new Promise((resolve) => setTimeout(resolve, 300));
      if (updater !== this.updater) return;

      this.heights.splice(0, animationNum, ...new Array(animationNum).fill(0));

      animationIndex += animationNum;

      const back = animationIndex - rowLength;
      if (back >= 0) animationIndex = back;

      this.animationIndex = animationIndex;
      this.animationHandler = setTimeout(animation, waitTime - 300);
    },
    stopAnimation() {
      const { animationHandler, updater } = this;

      this.updater = (updater + 1) % 999999;

      if (!animationHandler) return;

      clearTimeout(animationHandler);
    },
  },
  destroyed() {
    const { stopAnimation } = this;

    stopAnimation();
  },
};
</script>

<style lang="scss">
$color: #ed2b26;

.dvz-scroll-ranking-board {
  width: 100%;
  height: 100%;
  color: #fff;
  overflow: hidden;

  .row-item {
    transition: all 0.3s;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
  }

  .ranking-info {
    display: flex;
    width: 100%;
    font-size: 22px;

    .rank {
      width: 20px;
      color: $color;
    }

    .info-name {
      flex: 1;
      white-space: nowrap;
      text-overflow: ellipsis;
      width: 60%;
      overflow: hidden;
    }
  }

  .ranking-value {
    font-size: 22px;
  }

  .ranking-column {
    background-color: rgba(255, 255, 255, 0.08);
    margin-top: 5px;

    .inside-column {
      position: relative;
      height: 7px;
      background-color: $color;
      margin-bottom: 1px;
      border-radius: 1px;
      overflow: hidden;
    }

    .shine {
      position: absolute;
      left: 0%;
      top: 2px;
      height: 2px;
      width: 50px;
      transform: translateX(-100%);
      background: radial-gradient(rgb(243, 150, 150) 5%, transparent 80%);
      animation: shine 3s ease-in-out infinite alternate;
    }
  }
}

@keyframes shine {
  80% {
    left: 0%;
    transform: translateX(-100%);
  }

  100% {
    left: 100%;
    transform: translateX(0%);
  }
}
</style>
